/*
 * Copyright (C) 2012 DataSymphony (http://datasymphony.com.au)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package au.com.ds.restty.google;

import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.io.IOException;
import java.net.URI;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import au.com.ds.restty.ExecutionContext;
import au.com.ds.restty.Out;
import au.com.ds.restty.exc.PluginError;
import au.com.ds.restty.google.TokenCache.TokenDetails;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;

public class GoogleTokenProvider {
	private static final Logger LOG = LoggerFactory.getLogger(GoogleTokenProvider.class); 
	private static final String CLENT_ID = "597789832158.apps.googleusercontent.com";
	private static final String CLENT_SECRET = "-2uJ9TSLr6sEg30vgyueOZLZ";
	private static final String BROWSER = "google-chrome";

	private GoogleClientSecrets mSecrets;
	private HttpTransport mTransport;
	private JsonFactory mJsonFactory;
	private CodeReceiver mCodeReceiver;
	private TokenCache mTokenCache;
	
	public String getToken(Iterable<String> scope, String tag, boolean useCache) {
		String token = null;
		Credential credential = null;
		
		Date now = new Date();
		TokenDetails cachedData = null;
		if (useCache) {
			// use cached token (if not expired)
			if (mTokenCache == null) {
				mTokenCache = new TokenCache(ExecutionContext.get().getConfig().getWorkDir());
			}
			
			cachedData = mTokenCache.get(tag);
			if (cachedData != null) {
				Date expires = new Date(cachedData.getExpires());
				if (expires.after(now)) {
					token = cachedData.getAcceasToken();
				}
			}
		}
		
		//prepare google auth flow
		GoogleAuthorizationCodeFlow flow = null;
		if (token == null) {
			GoogleClientSecrets.Details secretDetails = new GoogleClientSecrets.Details();
			secretDetails.setClientId(CLENT_ID);
			secretDetails.setClientSecret(CLENT_SECRET);

			mSecrets = new GoogleClientSecrets();
			mSecrets.setInstalled(secretDetails);

			mTransport = new ApacheHttpTransport();
			mJsonFactory = new JacksonFactory();

			flow = new GoogleAuthorizationCodeFlow.Builder(
					mTransport,
					mJsonFactory,
					mSecrets,
					scope)
			.build();
			
			// try to refresh
			if (cachedData != null) {
				GoogleTokenResponse restoredResponse = new GoogleTokenResponse();
				restoredResponse.setRefreshToken(cachedData.getRefreshToken());
				credential = flow.createAndStoreCredential(restoredResponse, null);
				try {
					if (credential.refreshToken()) {
						token = credential.getAccessToken();
					} else {
						LOG.warn("Could not refresh token.");
					}
					
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		// finally try to ask user to grant access
		boolean hadInteraction = false;
		if (token == null) {
			mCodeReceiver = new CodeReceiver();
			
			try {
				// prepare Google authentication url and navigate browser to it
				String redirectUrl = mCodeReceiver.getRedirectUri();
				String authUrl = flow.newAuthorizationUrl().setRedirectUri(redirectUrl).build();
				browse(authUrl);
				
				// get authentication code
				String code = mCodeReceiver.waitForCode();
				mCodeReceiver.stop();
				
				GoogleTokenResponse tokenResponse = flow.newTokenRequest(code).setRedirectUri(redirectUrl).execute();
				credential = flow.createAndStoreCredential(tokenResponse, null);
				token = credential.getAccessToken();
				hadInteraction = true;
			} catch (Exception e) {
				throw new PluginError("Error during interactive authentication handling", "restty-oauth2", e);
			}
		}
		
		if (credential != null && token != null && useCache) {
			long expires = now.getTime() + credential.getExpiresInSeconds() * 1000 - 5000;
			String cacheFileName = mTokenCache.put(tag, token, expires, credential.getRefreshToken());
			if (hadInteraction) {
				Out.normal("ATTENTION! Authentication information has been saved to file \"" + 
						cacheFileName + "\" for the future use. You may always remove this " +
						"file and change the currnt program behaviour in the config file.");
			}
		}
		
		return token;
	}

	private static void browse(String url) {
		// first try the Java Desktop
		if (Desktop.isDesktopSupported()) {
			Desktop desktop = Desktop.getDesktop();
			if (desktop.isSupported(Action.BROWSE)) {
				try {
					desktop.browse(URI.create(url));
					return;
				} catch (IOException e) {
					// handled below
				}
			}
		}
		
		// Next try rundll32 (only works on Windows)
		try {
			Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
			return;
		} catch (IOException e) {
			// handled below
		}
		
		// Next try the requested browser (e.g. "google-chrome")
		if (BROWSER != null) {
			try {
				Runtime.getRuntime().exec(new String[] {BROWSER, url});
				return;
			} catch (IOException e) {}
		}
		
		// Finally just ask user to open in their browser using copy-paste
		Out.normal("Please open the following URL in your browser: " + url);
	}	
}
